/** stream.d - The stream base class

Authors:		
	Wei Li (oldrev@gmail.com)

Copyright:
	Copyright (C) 2007 Wei Li.	

License:		
	BSD
*/

module dotmars.io.stream;

import dotmars.base.math;
import dotmars.platform.base;

public import dotmars.base.stdtypes;
public import dotmars.io.ioexcept;

version(Win32) {

	enum SeekOrigin : DWORD
	{
		Begin = FILE_BEGIN,
		Current = FILE_CURRENT,
		End = FILE_END
	}

} //version(win32)

version(Posix)
{

	import dotmars.runtime.stdclib.stdio;

	enum SeekOrigin : int
	{
		Begin = SEEK_SET,
		Current = SEEK_CUR,
		End = SEEK_END
	}

} //version(posix)

////////////////////////////////////////////////////////////////////////////////

public abstract class Stream : IDisposable
{
	// --- The following methods must be overrided:
	public abstract bool readable();
	public abstract bool writable();
	public abstract bool seekable();

	public abstract void write(void* data, size_t count);
	public abstract size_t read(void* data, size_t count);

	public abstract long seek(long offset, SeekOrigin origin);
	public abstract void length(long len);

	public abstract void flush();
	public abstract void close(); // IDisposable.close
	// ---

	public this() { }

	public ~this() {
	}

	/** Copy a part of data from current position
	  *
	  *
	  */
	public long copyFrom(Stream s, long len) 
	in
	{
		assert(s !is null);
		assert(len > 0);
		assert(!s.eos);
	}
	body
	{
		long total = 0;
		ubyte[4096] buffer;
		while (len > 0) 
		{
			size_t count = len < buffer.length ? len : buffer.length;
			count = s.read(buffer.ptr, count);
			write(buffer.ptr, count);
			len -= count;
			total += count;
		}
		return total;
	}

	/**
	  * Copy all data from current position
	  */
	public long copyFrom(Stream s)
	in {
		assert(s !is null);
		assert(!s.eos);
	}
	body {
		assert(s.readable);
		assert(this.writable);

		long total = 0;
		ubyte[4096] buffer;
		while (!s.eos()) 
		{
			size_t count = s.read(buffer.ptr, buffer.length);
			write(buffer.ptr, count);
			total += count;
		}
		return total;
	}

	public void write(void[] data)
	in {
		assert(data !is null);
		assert(data.length > 0);
	}
	body {
		write(data.ptr, data.length);
	}

	public void writeByte(ubyte b) {
		write(&b, 1);
	}

	public size_t read(void[] data)
	in {
		assert(data !is null);
		assert(data.length > 0);
	}
	body {
		size_t r = read(data.ptr, data.length);
		return r;
	}

	public int readByte()
	{
		ubyte b;
		size_t l = read(&b, 1);
		return l > 0 ? b : -1;
	}

	public long length()
	in {
		assert(seekable);
	}
	out(r) {
		assert(r >= 0);
	}
	body {
		long curpos = position;
		long ret = seek(0, SeekOrigin.End);
		position = curpos;
		return ret;
	}

	public long position()
	out(r) {
		assert(r >= 0);
	}
	body {
		return seek(0, SeekOrigin.Current);
	}

	public void position(long newPos)
	in {
		assert(newPos < length); 
	}
	body {
		seek(newPos, SeekOrigin.Begin);
	}

	public bool eos() {
		assert(seekable);
		return position >= length;
	}


	public void[] opSlice()
	in {
		assert(readable);
		assert(seekable);
	}
	body {
		ubyte[] buf = new ubyte[this.length]; 
		size_t n = read(buf);
		if(n != buf.length)
			throw new IOException("Failed to read file content");
		return buf;
	}

	public void[] opSlice(long start, long end)
	in {
		assert(readable);
		assert(seekable);
		assert(start >= 0);
		assert(end > start);
		assert(end <= length);
	}
	body {
		long oldpos = position;
		position = start;
		ubyte[] buf = new ubyte[end - start];
		size_t n = read(buf);
		if(n != end - start)
			throw new IOException("Failed to read file content");
		position = oldpos;
		return buf;
	}

	public ubyte opIndex(long pos)
	in {
		assert(readable);
		assert(seekable);
		assert(pos >= 0 && pos < length);
	}
	body {
		long oldpos = position;
		position = pos;
		ubyte r = readByte();
		position = oldpos;
		return r;
	}

	public ubyte opIndexAssign(ubyte rhs, long pos)
	in {
		assert(writable);
		assert(seekable);
		assert(pos >= 0 && pos < length);
	}
	body {
		long oldpos = position;
		position = pos;
		writeByte(rhs);
		position = oldpos;
		return rhs;
	}

	// Stream >> buffer;
	public void opShr(void[] rhs)
	in {
		assert(rhs !is null);
	}
	body {
		if(read(rhs) != rhs.length)
			throw new IOException("Failed to read such stream");
	}

	//Stream << buffer;
	public Stream opShl(void[] rhs)
	in {
		assert(rhs !is null);
	}
	body {
		write(rhs);
		return this;
	}
}


////////////////////////////////////////////////////////////////////////////////
// Stream adapters:

final class CombinedStream : Stream
{
	private Stream m_ins;
	private Stream m_outs;
	private bool m_leaveOpen;

	public this(Stream inStream, Stream outStream, bool leaveOpen = false)
	in {
		assert(inStream !is null);
		assert(outStream !is null);
		assert(inStream.readable);
		assert(outStream.writable);
	}
	body {
		m_ins = inStream;
		m_outs = outStream;
		m_leaveOpen = leaveOpen;
	}

	public ~this() {
		close();
	}

	public override bool readable() { return true; }

	public override bool writable() { return true; }

	public override bool seekable() { return false; }

	public override void write(void* data, size_t count) {
		return m_outs.write(data, count);
	}

	public override size_t read(void* data, size_t count) {
		return m_ins.read(data, count);
	}

	public override long seek(long offset, SeekOrigin origin) {
		assert(false, "Not supported");
	}

	public override void length(long len) {
		assert(false, "Not supported");
	}

	public override void flush() {
		m_outs.flush();
	}
	
	public override void close() 
	{ 
		if(!m_leaveOpen) 
		{
			m_ins.close();
			m_outs.close();
		}
	}

	public override bool eos()
	{
		return m_ins.eos;
	}

}


final class RangedStream : Stream
{
	private Stream m_stream;
	private long m_limitedSize;
	private bool m_leaveOpen;
	private long m_origin;

	public this(Stream s, long limitedSize, bool leaveOpen = false)
	in {
		assert(s !is null);
		if(s.seekable)
			assert(limitedSize <= s.length - s.position);
	}
	body {
		m_stream = s;
		m_limitedSize = limitedSize;
		m_leaveOpen = leaveOpen;
		m_origin = s.seekable ? s.position : 0;
	}

	public ~this() {
		close();
	}

	public override bool readable() { return m_stream.readable; }

	public override bool writable() { return false; }

	public override bool seekable() { return m_stream.seekable; }

	public override void write(void* data, size_t count) 
	{
		assert(false, "Not supported");
	}

	public override size_t read(void* data, size_t count) 
	in {
		assert(readable);
		assert(data !is null);
	}
	body {
		size_t r;

		if(!seekable) {
			count = min(cast(long)count, m_limitedSize - m_origin);
			r = m_stream.read(data, count);
			m_origin += r;
		}
		else {
			count = min(cast(long)count, m_limitedSize - (m_stream.position - m_origin));
			r = m_stream.read(data, count);
		}

		return r;
	}

	public override long seek(long offset, SeekOrigin origin) 
	in {
		assert(seekable);
		assert(abs(offset) < m_limitedSize);
	}
	body {
		return m_stream.seek(m_stream.position + offset, origin) - m_origin;
	}

	public override void length(long len) {
		assert(false, "Not supported");
	}

	public override void flush() {
		m_stream.flush();
	}
	
	public override void close() 
	{ // IDisposable.close 
		if(!m_leaveOpen) {
			m_stream.close();
		}
	}
}
